#ifndef B2D_ICONTEXT_H
#define B2D_ICONTEXT_H

#include "Singleton.h"
#include "hashmap.h"
#include "Object.h"

NS_B2D_BEGIN

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Interface for the global application context. This acts as a depency injection
/// 		container. It associates an instance with a given type.
////////////////////////////////////////////////////////////////////////////////////////////////////
class B2D_API IContext
	: public Singleton<IContext>
{
	B2D_INTERFACE(IContext)

public:
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Registers the subsystem provided with the interface.
	///
	/// \tparam	typename TInterface	Type of interface.
	/// \tparam	typename TInstance	Type of instance.
	/// \param [in]	sub	The instance to register.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	template<typename TInterface, typename TInstance>
	void registerInstance(TInstance& inst)
	{
		static_assert(std::is_base_of<TInterface,TInstance>::value,
			"The subsystem must derive from the provided interface.");
		registerInstanceImpl(TypeToId<TInterface>::Id(), &inst);
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Unregisters the instance associated with the given interface.
	///
	/// \tparam	typename TInterface	Type of interface.
	///
	/// \return	null if it fails, else the unregistered instance.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	template<typename TInterface>
	TInterface* unregisterInstance(void)
	{
		void* ptr = unregisterInstanceImpl(TypeToId<TInterface>::Id());
		return static_cast<TInterface*>(ptr);
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Gets the instance associated with a given interface.
	///
	/// \tparam	typename TInterface	Type of interface.
	///
	/// \return	null if it fails, else the instance.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	template<typename TInterface>
	TInterface* getInstance(void) const
	{
		void* sub = getInstanceImpl(TypeToId<TInterface>::Id());
		return static_cast<TInterface*>(sub);
	}

private:
	virtual void registerInstanceImpl(size_t id, void* ptr) = 0;
	virtual void* unregisterInstanceImpl(size_t id) = 0;
	virtual void* getInstanceImpl(size_t id) const = 0;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Default implementation of the context.
////////////////////////////////////////////////////////////////////////////////////////////////////
class B2D_API DefaultContext final
	: public IContext
{
private:
	typedef hashmap<size_t, void*> InstanceMap;
	InstanceMap mInstances;

private:
	void registerInstanceImpl(size_t id, void* ptr)
	{
		// If already registered, rewrite with new subsystem
		// otherwise, just add to the map
		mInstances[id] = ptr;
	}

	void* unregisterInstanceImpl(size_t id)
	{
		const auto it = mInstances.find(id);
		if( it != mInstances.end() )
		{
			void* ptr = it->second;
			mInstances.erase(it);
			return ptr;
		}
		return nullptr;
	}

	void* getInstanceImpl(size_t id) const
	{
		const auto it = mInstances.find(id);
		return (it == mInstances.end()) ? nullptr : it->second;
	}
};

FORCE_INLINE const IContext& GetContext(void) { return IContext::Get(); }
FORCE_INLINE const IContext* GetContextPtr(void) { return IContext::GetPtr(); }
FORCE_INLINE IContext& GetContext_RW(void) { return IContext::Get(); }
FORCE_INLINE IContext* GetContextPtr_RW(void) { return IContext::GetPtr(); }

NS_B2D_END

#endif
